package spider

import (
	"asocrawler/conf"
	"asocrawler/db"
	"asocrawler/tool"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (

	/* APP */
	VivoAppRank = "https://api.qimai.cn/rank/marketRank"

	VivoAppRankHtmlUrl = "https://www.qimai.cn/rank/marketRank/market/%s/category/%s/date/%s"
)

type VivoAppSpider struct {
	ChannelName  string
	TimeInterval int
	Platform     string
	AppIconMap   map[string]int
}

func NewVivoAppDriver() (v *VivoAppSpider) {
	v = &VivoAppSpider{
		ChannelName: "vivo",
		Platform:    "Android",
		AppIconMap:  make(map[string]int),
	}
	return
}

func (this VivoAppSpider) GetWorkerInfo() (w tool.WorkerInfo) {
	w.ChannelName = this.ChannelName
	return w
}
func (this VivoAppSpider) Run() {
	go func() {
		this.run()
	}()
}
func (this *VivoAppSpider) Println(v ...interface{}) {
	var str = fmt.Sprintf("[%s]-[%s]", this.ChannelName, this.Platform)
	if conf.Conf.Debug.Switch {
		str += "-[Debug]"
	}
	log.Println(str, fmt.Sprintln(v...))
}

func (this *VivoAppSpider) vivoAppDataRequest(analysisP, marketP, coategoryP, date string) (p db.AppResult, err error) {
	client := &http.Client{}
	data := url.Values{}

	data.Add("analysis", analysisP)
	data.Add("market", marketP)
	data.Add("category", coategoryP)
	data.Add("date", date)

	req, err := http.NewRequest("GET", VivoAppRank+"?"+data.Encode(), nil)
	if err != nil {
		return
	}

	req.Header.Set("referer", tool.QiMai_Referer)
	req.Header.Set("Origin", tool.QiMai_Origin)
	req.Header.Set("User-Agent", tool.QiMai_UserAgent)
	req.Header.Set("cookie", tool.QiMai_Cookie)

	resp, err := client.Do(req)
	if err != nil {
		time.Sleep(1 * time.Second)
		return
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	err = json.Unmarshal(b, &p)
	if err != nil {
		this.Println("string(b):", string(b))
		return
	}
	if p.Code != 10000 {
		err = errors.New("p.Code != 0,p=" + string(b))
		return
	}
	return
}

func (this *VivoAppSpider) collectVivoAppData(date string) (rs []db.DbDataResult, err error) {

	paramsMap := make(map[string]db.Params)
	paramsMap["单机榜"] = db.Params{
		M: "8",
		C: "5",
	}
	paramsMap["网游榜"] = db.Params{
		M: "8",
		C: "6",
	}

	for rankType, pm := range paramsMap {
		intervalTime := tool.GetRandomMillSecondNum()
		time.Sleep(intervalTime * time.Millisecond)

		var vs db.DbDataResult
		vs.Date = date
		vs.ChannelName = this.ChannelName
		vs.Platform = this.Platform
		vs.RankType = rankType

		var rankInfo []db.AppResultInfo

		if conf.Conf.QiMaiCollectMode == "api" {
			// 接口获取

			rankInfo, err = this.collectDataByApi(pm, date)
			if err != nil {
				this.Println(rankType+":"+"this.collectDataByApi error:", err)
				return
			}
		} else {
			// html解析

			rankInfo, err = this.collectDataByHtml(pm, date)
			if err != nil {
				this.Println(rankType+":"+"this.collectDataByHtml error:", err)
				return
			}
		}

		for _, r := range rankInfo {
			var downloadNum int
			nVal, flag := r.DownloadNum.(string)
			if flag {
				//如果 r.DownloadNum 是字符串
				downloadNumStr := strings.Replace(nVal, ",", "", -1)
				downloadNum, err = strconv.Atoi(downloadNumStr)
				if err != nil {
					this.Println("strconv.Atoi error:", err)
					return rs, err
				}
			}

			dIndex, err := strconv.Atoi(r.RankInfo.Ranking)
			if err != nil {
				return rs, err
			}

			appId := r.AppInfo.AppId

			if this.AppIconMap[appId] == 0 {
				//如果icon表中没有图片，则去下载
				err := db.GetAppIcon(r.AppInfo.Icon, r.AppInfo.AppId, this.ChannelName, this.Platform)
				if err != nil {
					this.Println("getAppIcon error,err=", err, r.AppInfo.Icon)
				}
				//标记 已存 防止同一id 多次存入icon表
				this.AppIconMap[appId] = 1
			}

			// 获取详情
			var detailInfo db.ApiData
			var v db.ApiData
			if conf.Conf.QiMaiCollectMode == "api" {
				// 接口获取详情
				time.Sleep(intervalTime * time.Millisecond)

				detailInfo, err = this.collectAppDetail(r.AppInfo.AppId, pm.M)
				if err != nil {
					if err.Error() == "ip被禁用" {
						this.Println("collectAppDetail ip被禁用，退出")
						return rs, err
					}
					this.Println("collectAppDetail error,err =", err)
					continue
				}

				v = db.ApiData{
					AppId:       r.AppInfo.AppId,
					GameName:    r.AppInfo.AppName,
					PlayCount:   0,
					TypeName:    r.RankInfo.Genre,
					DIndex:      dIndex - 1, //从0开始
					BRank:       1,
					DownloadNum: downloadNum,
					Score:       fmt.Sprint(r.AppInfo.AppCommentScore),
					Company:     r.Company.Name,
					OnSaleDate:  detailInfo.OnSaleDate,
					GameDesc:    detailInfo.GameDesc,
					GameVersion: detailInfo.GameVersion,

					TotalDownloadCount: detailInfo.TotalDownloadCount,
					CommentCount:       detailInfo.CommentCount,
					OriginalUrl:        detailInfo.OriginalUrl,
				}
			} else {
				// todo html解析详情

				v = db.ApiData{
					AppId:       r.AppInfo.AppId,
					GameName:    r.AppInfo.AppName,
					PlayCount:   0,
					TypeName:    r.RankInfo.Genre,
					DIndex:      dIndex - 1, //从0开始
					BRank:       1,
					DownloadNum: downloadNum,
					Score:       fmt.Sprint(r.AppInfo.AppCommentScore),
					Company:     r.Company.Name,
					//OnSaleDate:  detailInfo.OnSaleDate,
					//GameDesc:    detailInfo.GameDesc,
					//GameVersion: detailInfo.GameVersion,
					//
					//TotalDownloadCount: detailInfo.TotalDownloadCount,
					//CommentCount:       detailInfo.CommentCount,
					//OriginalUrl:        detailInfo.OriginalUrl,
				}
			}

			vs.Data = append(vs.Data, v)

		}

		rs = append(rs, vs)
	}

	return

}

// 模式1 从接口中获取数据
func (this *VivoAppSpider) collectDataByApi(pm db.Params, date string) (rs []db.AppResultInfo, err error) {
	tryTimes := 0

	aUrl := "/rank/marketRank"
	aParams := fmt.Sprintf(`{"market":%s,"category":%s,"date":"%s"}`, pm.M, pm.C, date)

	pm.A, err = tool.GetAnalysisParams(aUrl, aParams)
	if err != nil {
		this.Println("getAnalysisParams error,err=", err)
		return rs, err
	}
re:
	p, err := this.vivoAppDataRequest(pm.A, pm.M, pm.C, date)
	if err != nil {
		if p.BanIp != "" {
			return rs, errors.New("ip被禁用,退出")
		} else if tryTimes < 20 {
			this.Println(" error,collectVivoAppData 请求失败,重新请求中", err)
			tryTimes++
			time.Sleep(200 * time.Millisecond)
			goto re
		}
		err = errors.New("collectVivoAppData 多次请求失败,退出")
		return rs, err
	}

	rs = p.RankInfo

	return
}

// 模式2 从html中解析
func (this *VivoAppSpider) collectDataByHtml(pm db.Params, date string) (rs []db.AppResultInfo, err error) {
	var pUrl = fmt.Sprintf(VivoAppRankHtmlUrl, pm.M, pm.C, date)
	rs, err = tool.GetQiMaiDataByHtml(pUrl)
	if err != nil {
		this.Println("解析七麦HTML文本失败，err=", err)
	}

	return

}

// 获取详情数据
func (this *VivoAppSpider) collectAppDetail(appId, market string) (res db.ApiData, err error) {
	var tryTimes = 0
re:
	aParams := fmt.Sprintf(`{"appid":%s,"market":%s}`, appId, market)
	analysisP, err := tool.GetAnalysisParams(tool.QiMai_Android_Detail_Api, aParams)
	if err != nil {
		return
	}

	client := &http.Client{}
	data := url.Values{}

	data.Add("analysis", analysisP)
	data.Add("market", market)
	data.Add("appid", appId)

	req, err := http.NewRequest("GET", tool.QiMai_Android_Detail_Api+"?"+data.Encode(), nil)
	if err != nil {
		return
	}

	req.Header.Set("referer", tool.QiMai_Referer)
	req.Header.Set("Origin", tool.QiMai_Origin)
	req.Header.Set("User-Agent", tool.QiMai_UserAgent)
	req.Header.Set("cookie", tool.QiMai_Cookie)

	resp, err := client.Do(req)
	if err != nil {
		time.Sleep(1 * time.Second)
		return
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	var p db.AppDetailResultInfo
	err = json.Unmarshal(b, &p)
	if err != nil {
		this.Println("json.Unmarshal error,尝试重新请求,string(b)=", string(b), tool.QiMai_Android_Detail_Api+"?"+data.Encode())
		if tryTimes < 20 {
			tryTimes++
			goto re
		}
		this.Println("json.Unmarshal 多次请求失败，退出")
		return
	}
	if p.Code != 10000 {
		if p.BanIp != "" {
			return res, errors.New("ip被禁用")
		}
		if tryTimes < 20 {
			this.Println("code != 10000,尝试重新请求", tool.QiMai_Android_Detail_Api+"?"+data.Encode())
			tryTimes++
			goto re
		}
		this.Println("code != 10000 多次请求失败，退出")
		err = errors.New("p.Code != 0,p=" + string(b))
		return
	}

	appIdInt, err := strconv.Atoi(appId)
	if err != nil {
		return
	}
	marketInt, err := strconv.Atoi(market)
	if err != nil {
		return
	}

	var commentCount, totalDownloadCount int
	commentCountStr, flag := p.AppInfo.AppCommentCount.(string)
	if flag {
		commentCount, err = strconv.Atoi(commentCountStr)
		if err != nil {
			this.Println("strconv.Atoi(p.AppInfo.AppCommentCount) err,err=", err)
		}
	}

	downloadNumStr, flag := p.AppInfo.AppDownloadNum.(string)
	if flag {
		totalDownloadCount, err = strconv.Atoi(downloadNumStr)
		if err != nil {
			this.Println("strconv.Atoi(p.AppInfo.AppDownloadNum) err,err=", err)
		}
	}

	// 从详情接口获取的数据
	res.OriginalUrl = fmt.Sprintf(tool.QiMai_Detail, appIdInt, marketInt)
	res.OnSaleDate = p.AppInfo.AppIdAddTime
	res.CommentCount = commentCount
	res.TotalDownloadCount = totalDownloadCount
	res.GameDesc = tool.Html2Text(p.AppInfo.AppBrief)
	res.GameVersion = p.AppInfo.AppVersion

	return
}

func (this *VivoAppSpider) collectApp(date string) (bool, error) {
	rankTypeList, err := db.QueryRankTypeList(this.ChannelName, this.Platform)
	if err != nil {
		this.Println("collectApp QueryRankTypeList err:", err)
		return false, err
	}

	rs, err := this.collectVivoAppData(date)
	if err != nil {
		return false, err
	}
	for _, r := range rs {
		//获取排行榜ID和表后缀
		r.TablePostfix = rankTypeList[r.RankType].TablePostfix
		r.RankTypeId = rankTypeList[r.RankType].Id

		if len(r.Data) == 0 {
			return false, errors.New("collectApp r 长度为空")
		} else {
			err = db.SaveData(r)
			if err != nil {
				return false, err
			}
		}
	}
	return true, nil
}

func (this *VivoAppSpider) Collect(date string) (bool, error) {
	var err error
	this.AppIconMap, err = db.GetAppIconMap(this.ChannelName, this.Platform)
	if err != nil {
		this.Println("db.GetAppIconMap error,err=", err)
		return false, err
	}
	bol2, err2 := this.collectApp(date)
	if err2 != nil {
		this.Println("collectApp err = ", err2)
		return bol2, err2
	}
	return true, nil

}

func (this *VivoAppSpider) run() {
	if conf.Conf.Debug.Switch {
		tool.Worker.Post(this)
	} else {
		//启动时执行一次
		tool.Worker.Post(this)
		for {
			hour := time.Now().Hour()
			if hour == 7 || hour == 21 {
				tool.Worker.Post(this)
			}
			time.Sleep(1 * 60 * time.Minute)
		}
	}
}
